# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: api\handlers\frontend\reports.py
__author__ = "sanyi"
import datetime, logging, uuid, time, os.path, json
from dateutil.tz import tzlocal
from sqlalchemy import desc as sql_desc
from sqlalchemy import distinct as sql_distinct
from sqlalchemy import func as sql_func
from sqlalchemy import select as sql_select
from sqlalchemy.sql.expression import nullslast as sql_nulls_last
from api.classes.frontend.auto import *
from api.filters import Filters
from api.filters.sqlalchemy_adapter import get_sqlachemy_filter
from api.handlers.frontend import FrontendResourceHandler
from db import Connection
from db.data.access import (
    generate_scan_session_query,
    generate_target_vulns_query,
    generate_scan_ext_query,
)
from db.data.access import generate_scan_session_vulns_query
from db.data.access import (
    generate_target_query,
    apply_filters,
    generate_report_access_query,
    generate_group_query,
)
from db.data.users import get_owner_id
from db.tables.events import create_event, EventResources
from db.tables.groups import GroupRow
from db.tables.report_templates import ReportTemplateRow, ReportTemplatesTable
from db.tables.reports import ReportRow, ReportsTable
from db.tables.scan_session_vulns import ScanSessionVulnRow, ScanSessionVulnsTable
from db.tables.scan_sessions import ScanSessionRow, ScanSessionsTable
from db.tables.scans import ScanRow, ScansTable
from db.tables.target_vulns import TargetVulnRow, TargetVulnsTable
from db.tables.targets import TargetRow, TargetsTable
from server.http_responses.http_status_codes import *
from server.http_responses.http_errors import (
    Http409,
    Http403,
    Http404,
    Http400,
    ApiErrors,
    Http401,
)
from helpers.licensing.features import (
    Features,
    is_report_id_compliance_id,
    is_report_id_waf_export_id,
)
from helpers.setup_types import *
from settings_provider import settings
from helpers.hashing import hmac256hex, random_hex_digits
from db.views.scans_extended import ScanExtendedViewRow

logger = logging.getLogger("frontend_api_resources.Report")
report_filters = {
    "keys": {
        "template_id": {
            "value_type": "uuid4",
            "multiple_values": True,
            "operators": ("eq", "ne"),
        },
        "source": {
            "value_type": "string",
            "multiple_values": True,
            "allowed_values": (
                "all_vulnerabilities",
                "targets",
                "groups",
                "scans",
                "scan_result",
                "vulnerabilities",
                "scan_vulnerabilities",
            ),
        },
        "status": {
            "value_type": "string",
            "multiple_values": True,
            "allowed_values": ("queued", "processing", "completed", "failed"),
        },
        "created": {
            "value_type": "date",
            "multiple_values": True,
            "operators": ("eq", "lt", "gt", "ge", "le"),
        },
    }
}


def get_download_descriptor(f):
    descriptor = "%s%08x%s" % (random_hex_digits(8), int(time.time() + 3600), f)
    return (
        hmac256hex(descriptor, settings["server.frontend.session_secret"]) + descriptor
    )


class Reports(FrontendResourceHandler):
    report_cache = dict()

    def __get_report_data(self, report_template_id):
        report_data = self.report_cache.get("report_id")
        if not report_data:
            q = sql_select((ReportTemplateRow,)).where(
                ReportTemplateRow.report_template_id == report_template_id
            )
            with Connection(self.current_shard_name) as (db_connection):
                report_data = db_connection.execute(q).fetchone()
                if report_data:
                    report_data = dict(report_data)
        return report_data

    def __sanitise_report_source(self, new_report):
        if hasattr(new_report, "template_id"):
            template_id = new_report.template_id
        else:
            template_id = new_report.export_id
        report_data = self._Reports__get_report_data(template_id)
        if report_data is None:
            return Http409("Invalid report template")
        accepted_sources = report_data["data"].get("accepted_sources")
        list_type = new_report.source.list_type
        if isinstance(accepted_sources, (list, tuple)):
            if list_type not in accepted_sources:
                return Http409("Invalid report source")
        if list_type == "all_vulnerabilities":
            select_from, tail_filters = generate_target_vulns_query(self.request_user)
            query = sql_select((sql_distinct(TargetVulnRow.target_id),)).select_from(
                select_from
            )
            query = apply_filters(query, tail_filters)
            target_id_list = []
            with Connection(self.current_shard_name) as (db_connection):
                for target in db_connection.execute(query).fetchall():
                    target_id_list.append(target.target_id)

            if not target_id_list:
                return Http409("No targets", code=ApiErrors.NO_TARGET_FOR_ALL_REPORT)
            new_report.source.description = "All vulnerabilities"
            new_report.source.id_list = target_id_list
        else:
            if list_type == "targets":
                target_id_list = list(set(new_report.source.id_list))
                select_from, tail_filters = generate_target_query(self.request_user)
                q = (
                    sql_select((sql_func.count(TargetRow.target_id),))
                    .select_from(select_from)
                    .where(TargetRow.target_id.in_(target_id_list))
                )
                q = apply_filters(q, tail_filters)
                with Connection(self.current_shard_name) as (db_connection):
                    c = db_connection.execute(q).scalar()
                l = len(target_id_list)
                if l != c:
                    return Http403()
                if l == 0:
                    new_report.source.description = "Empty targets report"
                else:
                    if l == 1:
                        q = sql_select(
                            (TargetRow.address, TargetRow.description)
                        ).where(TargetRow.target_id == target_id_list[0])
                        with Connection(self.current_shard_name) as (db_connection):
                            t = db_connection.execute(q).fetchone()
                            new_report.source.description = (";").join(
                                (t.address, t.description)
                            )
                    else:
                        new_report.source.description = "Multiple Targets"
                new_report.source.id_list = target_id_list
            else:
                if list_type == "groups":
                    group_id_list = list(set(new_report.source.id_list))
                    select_from, tail_filters = generate_group_query(self.request_user)
                    query = (
                        sql_select((sql_func.count(GroupRow.group_id),))
                        .select_from(select_from)
                        .where(GroupRow.group_id.in_(group_id_list))
                    )
                    query = apply_filters(query, tail_filters)
                    with Connection(self.current_shard_name) as (db_connection):
                        c = db_connection.execute(query).scalar()
                    l = len(group_id_list)
                    if l != c:
                        return Http403()
                    if l == 0:
                        new_report.source.description = "Empty groups report"
                    else:
                        if l == 1:
                            q = sql_select((GroupRow.name,)).where(
                                GroupRow.group_id == group_id_list[0]
                            )
                            with Connection(self.current_shard_name) as (db_connection):
                                g = db_connection.execute(q).fetchone()
                                new_report.source.description = g.name
                        else:
                            new_report.source.description = "Multiple Groups"
                    new_report.source.id_list = group_id_list
                else:
                    if list_type in ("scans", "scan_pair"):
                        scan_id_list = list(set(new_report.source.id_list))
                        l = len(scan_id_list)
                        if list_type == "scan_pair":
                            if l != 2:
                                raise Http409("Not a scan pair")
                        select_from, tail_filters = generate_scan_ext_query(
                            self.request_user
                        )
                        query = (
                            sql_select((ScanExtendedViewRow.scan_id,))
                            .select_from(select_from)
                            .where(ScanExtendedViewRow.scan_id.in_(scan_id_list))
                            .order_by(
                                sql_nulls_last(ScanExtendedViewRow.current_start_date),
                                ScanExtendedViewRow.created,
                            )
                        )
                        query = apply_filters(query, tail_filters)
                        ordered_scan_id_list = []
                        with Connection(self.current_shard_name) as (db_connection):
                            for r in db_connection.execute(query).fetchall():
                                ordered_scan_id_list.append(r.scan_id)

                        if l != len(ordered_scan_id_list):
                            return Http403()
                        if l == 0:
                            new_report.source.description = "Empty scans report"
                        else:
                            select_from = ScansTable.join(
                                TargetsTable, TargetRow.target_id == ScanRow.target_id
                            )
                            q = (
                                sql_select((TargetRow.address, TargetRow.description))
                                .select_from(select_from)
                                .where(ScanRow.scan_id.in_(scan_id_list))
                                .group_by(TargetRow.target_id)
                                .limit(2)
                            )
                            with Connection(self.current_shard_name) as (db_connection):
                                tl = db_connection.execute(q).fetchall()
                                if len(tl) > 1:
                                    new_report.source.description = "Multiple targets"
                                else:
                                    new_report.source.description = (";").join(
                                        (tl[0].address, tl[0].description)
                                    )
                        new_report.source.id_list = ordered_scan_id_list
                    else:
                        if list_type in ("scan_result", "scan_result_pair"):
                            scan_session_id_list = list(set(new_report.source.id_list))
                            l = len(scan_session_id_list)
                            if list_type == "scan_result_pair":
                                if l != 2:
                                    raise Http409("Not a scan result pair")
                            select_from, tail_filters = generate_scan_session_query(
                                self.request_user
                            )
                            query = (
                                sql_select((ScanSessionRow.scan_session_id,))
                                .select_from(select_from)
                                .where(
                                    ScanSessionRow.scan_session_id.in_(
                                        scan_session_id_list
                                    )
                                )
                                .order_by(ScanSessionRow.start_date)
                            )
                            query = apply_filters(query, tail_filters)
                            ordered_scan_session_id_list = []
                            with Connection(self.current_shard_name) as (db_connection):
                                for r in db_connection.execute(query).fetchall():
                                    ordered_scan_session_id_list.append(
                                        r.scan_session_id
                                    )

                            if l != len(ordered_scan_session_id_list):
                                return Http403()
                            if l == 0:
                                new_report.source.description = "Empty scans report"
                            else:
                                select_from = ScanSessionsTable.join(
                                    TargetsTable,
                                    TargetRow.target_id == ScanSessionRow.target_id,
                                )
                                q = (
                                    sql_select(
                                        (TargetRow.address, TargetRow.description)
                                    )
                                    .select_from(select_from)
                                    .where(
                                        ScanSessionRow.scan_session_id.in_(
                                            scan_session_id_list
                                        )
                                    )
                                    .group_by(TargetRow.target_id)
                                    .limit(2)
                                )
                                with Connection(self.current_shard_name) as (
                                    db_connection
                                ):
                                    tl = db_connection.execute(q).fetchall()
                                    if len(tl) > 1:
                                        new_report.source.description = (
                                            "Multiple targets"
                                        )
                                    else:
                                        new_report.source.description = (";").join(
                                            (tl[0].address, tl[0].description)
                                        )
                            new_report.source.id_list = ordered_scan_session_id_list
                        else:
                            if list_type == "vulnerabilities":
                                vuln_id_list = list(set(new_report.source.id_list))
                                select_from, tail_filters = generate_target_vulns_query(
                                    self.request_user
                                )
                                query = (
                                    sql_select((sql_func.count(TargetVulnRow.vuln_id),))
                                    .select_from(select_from)
                                    .where(TargetVulnRow.vuln_id.in_(vuln_id_list))
                                )
                                query = apply_filters(query, tail_filters)
                                with Connection(self.current_shard_name) as (
                                    db_connection
                                ):
                                    c = db_connection.execute(query).scalar()
                                l = len(vuln_id_list)
                                if l != c:
                                    return Http403()
                                if l == 0:
                                    new_report.source.description = (
                                        "Empty vulnerabilities report"
                                    )
                                else:
                                    select_from = TargetVulnsTable.join(
                                        TargetsTable,
                                        TargetVulnRow.target_id == TargetRow.target_id,
                                    )
                                    q = (
                                        sql_select(
                                            (TargetRow.address, TargetRow.description)
                                        )
                                        .select_from(select_from)
                                        .where(TargetVulnRow.vuln_id.in_(vuln_id_list))
                                        .group_by(TargetRow.target_id)
                                        .limit(2)
                                    )
                                    with Connection(self.current_shard_name) as (
                                        db_connection
                                    ):
                                        tl = db_connection.execute(q).fetchall()
                                        if len(tl) > 1:
                                            new_report.source.description = (
                                                "Multiple targets"
                                            )
                                        else:
                                            new_report.source.description = (";").join(
                                                (tl[0].address, tl[0].description)
                                            )
                                new_report.source.id_list = vuln_id_list
                            else:
                                if list_type == "scan_vulnerabilities":
                                    vuln_id_list = list(set(new_report.source.id_list))
                                    select_from, tail_filters = generate_scan_session_vulns_query(
                                        self.request_user
                                    )
                                    query = (
                                        sql_select(
                                            (
                                                sql_func.count(
                                                    ScanSessionVulnRow.vuln_id
                                                ),
                                            )
                                        )
                                        .select_from(select_from)
                                        .where(
                                            ScanSessionVulnRow.vuln_id.in_(vuln_id_list)
                                        )
                                    )
                                    query = apply_filters(query, tail_filters)
                                    with Connection(self.current_shard_name) as (
                                        db_connection
                                    ):
                                        c = db_connection.execute(query).scalar()
                                    l = len(vuln_id_list)
                                    if l != c:
                                        return Http403()
                                    if l == 0:
                                        new_report.source.description = (
                                            "Empty vulnerabilities report"
                                        )
                                    else:
                                        select_from = ScanSessionVulnsTable.join(
                                            ScanSessionsTable,
                                            ScanSessionRow.scan_session_id
                                            == ScanSessionVulnRow.scan_session_id,
                                        ).join(
                                            TargetsTable,
                                            TargetRow.target_id
                                            == ScanSessionRow.target_id,
                                        )
                                        q = (
                                            sql_select(
                                                (
                                                    TargetRow.address,
                                                    TargetRow.description,
                                                )
                                            )
                                            .select_from(select_from)
                                            .where(
                                                ScanSessionVulnRow.vuln_id.in_(
                                                    vuln_id_list
                                                )
                                            )
                                            .group_by(TargetRow.target_id)
                                            .limit(2)
                                        )
                                        with Connection(self.current_shard_name) as (
                                            db_connection
                                        ):
                                            tl = db_connection.execute(q).fetchall()
                                            if len(tl) > 1:
                                                new_report.source.description = (
                                                    "Multiple targets"
                                                )
                                            else:
                                                new_report.source.description = (
                                                    ";"
                                                ).join(
                                                    (tl[0].address, tl[0].description)
                                                )
                                    new_report.source.id_list = vuln_id_list

    def repeat_report(self, report_id, **_):
        """
        The original report has to be accessible by the user
        :param report_id:
        :param _:
        :return:
        """
        select_from, tail_filters = generate_report_access_query(self.request_user)
        select_from = select_from.join(
            ReportTemplatesTable,
            ReportTemplateRow.report_template_id == ReportRow.report_template_id,
        )
        query = (
            sql_select((ReportRow, ReportTemplateRow.name.label("template_name")))
            .select_from(select_from)
            .where(ReportRow.report_id == report_id)
            .where(ReportRow.deleted_at.is_(None))
        )
        query = apply_filters(query, tail_filters)
        with Connection(self.current_shard_name) as (db_connection):
            report = db_connection.execute(query).fetchone()
            if not report or report.status != "completed":
                return Http404()
            report = dict(report)
        if not self.license.has_feature(Features.COMPLIANCE_REPORTS):
            if is_report_id_compliance_id(report["report_template_id"]):
                raise Http403()
        if not self.license.has_feature(Features.EXPORT_WAF):
            if is_report_id_waf_export_id(report["report_template_id"]):
                raise Http403()
        with Connection(self.current_shard_name) as (db_connection):
            report_id = str(uuid.uuid4())
            new_report_query = ReportsTable.insert().values(
                report_id=report_id,
                owner_id=get_owner_id(self.request_user),
                creator_id=self.request_user["user_id"],
                source=report["source"],
                report_template_id=report["template_id"],
                source_type=report["source_type"],
            )
            db_connection.execute(new_report_query)
            event = create_event(
                "report_asked",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                data=dict(
                    report_template_id=report["template_id"], source=report["source"]
                ),
                resource_type=EventResources.report,
                resource_id=report_id,
                shard=self.current_shard_name,
            )
            db_connection.execute(event)
            return Http201(location=self.base_path + "/reports/" + report_id)

    def generate_new_report(self, body, **_):
        """
        accessible by scan ownership
        :param body:
        :param _:
        :return:
        """
        new_report = ClsNewReport(**body)
        if not self.license.has_feature(Features.COMPLIANCE_REPORTS):
            if is_report_id_compliance_id(new_report.template_id):
                raise Http403()
        if not self.license.has_feature(Features.EXPORT_WAF):
            if is_report_id_waf_export_id(new_report.template_id):
                raise Http403()
        try:
            result = self._Reports__sanitise_report_source(new_report)
        except Exception:
            raise Http400()

        if result is not None:
            return result
        else:
            report_data = self._Reports__get_report_data(new_report.template_id)
            source = new_report.source.to_dict()
            source_type = new_report.source.list_type
            if source_type == "scan_pair":
                source_type = "scans"
            else:
                if source_type == "scan_result_pair":
                    source_type = "scan_result"
            report_id = str(uuid.uuid4())
            new_report_query = ReportsTable.insert().values(
                report_id=report_id,
                owner_id=get_owner_id(self.request_user),
                creator_id=self.request_user["user_id"],
                source=source,
                report_template_id=new_report.template_id,
                source_type=source_type,
            )
            with Connection(self.current_shard_name) as (db_connection):
                db_connection.execute(new_report_query)
                event = create_event(
                    "report_asked",
                    get_owner_id(self.request_user),
                    self.request_user["user_id"],
                    data=dict(
                        report_template_id=new_report.template_id,
                        source=source,
                        name=report_data["name"],
                    ),
                    resource_type=EventResources.report,
                    resource_id=report_id,
                    shard=self.current_shard_name,
                )
                db_connection.execute(event)
            return Http201(location=self.base_path + "/reports/" + report_id)

    def get_reports(self, l, q=None, c=None, **_):
        """
        accessible by report ownership
        :param l:
        :param q:
        :param c:
        :param _:
        :return:
        """
        limit = min(l, 100) if l is not None else 100
        c = int(c) if c is not None else 0
        cursor = c
        filters = Filters(report_filters, q)
        select_from, tail_filters = generate_report_access_query(self.request_user)
        select_from = select_from.join(
            ReportTemplatesTable,
            ReportTemplateRow.report_template_id == ReportRow.report_template_id,
        )
        query = (
            sql_select(
                (
                    ReportRow,
                    ReportTemplateRow.name.label("template_name"),
                    ReportTemplateRow.type.label("template_type"),
                )
            )
            .select_from(select_from)
            .where(ReportRow.deleted_at.is_(None))
            .where(ReportTemplateRow.type == 0)
        )
        query = apply_filters(query, tail_filters)
        query = (
            query.limit(limit)
            .offset(cursor)
            .order_by(sql_desc(ReportRow.created), ReportRow.report_id)
        )
        for filter_key, filter_expression in filters.data.items():
            if filter_key == "template_id":
                flt = get_sqlachemy_filter(
                    filter_expression,
                    ReportRow.report_template_id,
                    filters.descriptor["keys"][filter_key],
                )
                query = query.where(flt)
            elif filter_key == "created":
                flt = get_sqlachemy_filter(
                    filter_expression,
                    ReportRow.created,
                    filters.descriptor["keys"][filter_key],
                )
                query = query.where(flt)
            elif filter_key == "status":
                flt = get_sqlachemy_filter(
                    filter_expression,
                    ReportRow.status,
                    filters.descriptor["keys"][filter_key],
                )
                query = query.where(flt)
            elif filter_key == "source":
                flt = get_sqlachemy_filter(
                    filter_expression,
                    ReportRow.source_type,
                    filters.descriptor["keys"][filter_key],
                )
                query = query.where(flt)

        reports = []
        with Connection(self.current_shard_name) as (db_connection):
            for report in db_connection.execute(query).fetchall():
                download = None
                if report.status == "completed":
                    if is_setup_type_on_premise_master():
                        if "file" in report.result:
                            download = [
                                "/api/v1/reports/download/"
                                + get_download_descriptor(report.result["file"])
                            ]
                        else:
                            if "files" in report.result:
                                download = [
                                    "/api/v1/reports/download/"
                                    + get_download_descriptor(i)
                                    for i in report.result["files"]
                                ]
                    else:
                        if is_setup_type_aws():
                            from helpers.aws.s3 import generate_link

                            if "file" in report.result:
                                download = [
                                    generate_link(
                                        settings["storage.reports.s3.bucket"],
                                        settings["storage.reports.s3.dir"]
                                        + "/"
                                        + report.result["file"],
                                        3600,
                                    )
                                ]
                            else:
                                if "files" in report.result:
                                    download = [
                                        generate_link(
                                            settings["storage.reports.s3.bucket"],
                                            settings["storage.reports.s3.dir"]
                                            + "/"
                                            + i,
                                            3600,
                                        )
                                        for i in report.result["files"]
                                    ]
                        else:
                            download = []
                        report = ClsReport(
                            template_name=report.template_name,
                            generation_date=report.created,
                            report_id=report.report_id,
                            source=ClsReportSource(**report.source),
                            template_id=report.report_template_id,
                            status=report.status,
                            download=download,
                            template_type=report.template_type,
                        )
                        reports.append(report)

        l = len(reports)
        if l >= limit:
            next_cursor = cursor + l
        else:
            next_cursor = None
        return ClsReportListResponse(
            reports=reports,
            pagination=ClsPagination(next_cursor=next_cursor, previous_cursor=cursor),
        )

    def get_export(self, export_id, **_):
        return self.get_report(export_id)

    def get_report(self, report_id, **_):
        """
        accessible by report ownership
        :param report_id:
        :param _:
        :return:
        """
        select_from, tail_filters = generate_report_access_query(self.request_user)
        select_from = select_from.join(
            ReportTemplatesTable,
            ReportTemplateRow.report_template_id == ReportRow.report_template_id,
        )
        query = (
            sql_select(
                (
                    ReportRow,
                    ReportTemplateRow.name.label("template_name"),
                    ReportTemplateRow.type.label("template_type"),
                )
            )
            .select_from(select_from)
            .where(ReportRow.report_id == report_id)
            .where(ReportRow.deleted_at.is_(None))
        )
        query = apply_filters(query, tail_filters)
        with Connection(self.current_shard_name) as (db_connection):
            report = db_connection.execute(query).fetchone()
            if not report:
                return Http404()
            download = None
            if report.status == "completed":
                if is_setup_type_on_premise_master():
                    if "file" in report.result:
                        download = [
                            "/api/v1/reports/download/"
                            + get_download_descriptor(report.result["file"])
                        ]
                    else:
                        if "files" in report.result:
                            download = [
                                "/api/v1/reports/download/" + get_download_descriptor(i)
                                for i in report.result["files"]
                            ]
                else:
                    if is_setup_type_aws():
                        from helpers.aws.s3 import generate_link

                        if "file" in report.result:
                            download = [
                                generate_link(
                                    settings["storage.reports.s3.bucket"],
                                    settings["storage.reports.s3.dir"]
                                    + "/"
                                    + report.result["file"],
                                    3600,
                                )
                            ]
                        else:
                            if "files" in report.result:
                                download = [
                                    generate_link(
                                        settings["storage.reports.s3.bucket"],
                                        settings["storage.reports.s3.dir"] + "/" + i,
                                        3600,
                                    )
                                    for i in report.result["files"]
                                ]
                    else:
                        download = []
                    report = ClsReport(
                        template_name=report.template_name,
                        generation_date=report.created,
                        report_id=report.report_id,
                        source=ClsReportSource(**report.source),
                        template_id=report.report_template_id,
                        status=report.status,
                        download=download,
                        template_type=report.template_type,
                    )
                    return report

    @staticmethod
    def download_report(descriptor):
        if not is_setup_type_on_premise_master():
            return Http404()
        else:
            request_signature = descriptor[0:64]
            expiration = int(descriptor[72:80], 16)
            if time.time() > expiration:
                raise Http401()
            session_secret = settings["server.frontend.session_secret"]
            signature = hmac256hex(descriptor[64:], session_secret)
            if signature != request_signature:
                raise Http401()
            report_file_name = descriptor[80:]
            file_path = os.path.join(settings["storage.reports"], report_file_name)
            if not os.path.isfile(file_path):
                raise Http404()
            try:
                file_name = json.loads(open(file_path + ".info", "r").read())["name"]
            except Exception:
                file_name = report_file_name

            def file_generator():
                with open(file_path, "rb") as (infile):
                    while True:
                        data = infile.read(5120)
                        if not data:
                            return
                        yield data

            return Http200File(file_name, file_generator())

    def remove_export(self, export_id):
        return self.remove_report(export_id)

    def remove_report(self, report_id, **_):
        """
        accessible by report ownership
        :param report_id:
        :param _:
        :return:
        """
        select_from, tail_filters = generate_report_access_query(self.request_user)
        query = (
            sql_select((ReportRow,))
            .select_from(select_from)
            .where(ReportRow.report_id == report_id)
            .where(ReportRow.deleted_at.is_(None))
        )
        query = apply_filters(query, tail_filters)
        with Connection(self.current_shard_name) as (db_connection):
            report = db_connection.execute(query).fetchone()
            if report is None:
                return Http404()
            query = (
                ReportsTable.update(
                    values=dict(deleted_at=datetime.datetime.now(tzlocal()))
                )
            ).where(ReportRow.report_id == report_id)
            db_connection.execute(query)
        return Http204()

    def get_report_templates(self, **_):
        """
        accessible by anyone
        :param _:
        :return:
        """
        templates = []
        query = sql_select(
            (
                ReportTemplateRow.report_template_id,
                ReportTemplateRow.name,
                ReportTemplateRow.data,
            )
        ).where(ReportTemplateRow.type == 0)
        with Connection(self.current_shard_name) as (db_connection):
            for template in db_connection.execute(query).fetchall():
                if not self.license.has_feature(Features.COMPLIANCE_REPORTS):
                    if is_report_id_compliance_id(template.report_template_id):
                        continue
                    accepted_sources = template.data.get("accepted_sources")
                    if accepted_sources is None:
                        accepted_sources = [
                            "all_vulnerabilities",
                            "targets",
                            "groups",
                            "scans",
                            "scan_result",
                            "vulnerabilities",
                            "scan_vulnerabilities",
                            "scan_pair",
                            "scan_result_pair",
                        ]
                    templates.append(
                        ClsReportTemplate(
                            template_id=template.report_template_id,
                            name=template.name,
                            group=template.data.get("group"),
                            accepted_sources=accepted_sources,
                        )
                    )

        return ClsReportTemplateList(templates=templates)

    def get_export_types(self, **_):
        """
        accessible by anyone
        :param _:
        :return:
        """
        templates = []
        query = sql_select(
            (
                ReportTemplateRow.report_template_id,
                ReportTemplateRow.name,
                ReportTemplateRow.data,
            )
        ).where(ReportTemplateRow.type == 1)
        with Connection(self.current_shard_name) as (db_connection):
            for template in db_connection.execute(query).fetchall():
                if not self.license.has_feature(Features.EXPORT_WAF):
                    if is_report_id_waf_export_id(template.report_template_id):
                        continue
                    template = ClsExportType(
                        export_id=template.report_template_id,
                        name=template.name,
                        content_type=template.data.get("content_type"),
                        accepted_sources=template.data.get("accepted_sources"),
                    )
                    templates.append(template)

        return ClsExportTypesList(templates=templates)

    def export(self, body, **_):
        """
        accessible by scan ownership
        :param body:
        :param _:
        :return:
        """
        new_export = ClsNewExport(**body)
        if not self.license.has_feature(Features.COMPLIANCE_REPORTS):
            if is_report_id_compliance_id(new_export.export_id):
                raise Http403()
        if not self.license.has_feature(Features.EXPORT_WAF):
            if is_report_id_waf_export_id(new_export.export_id):
                raise Http403()
        result = self._Reports__sanitise_report_source(new_export)
        if result is not None:
            return result
        else:
            report_data = self._Reports__get_report_data(new_export.export_id)
            report_id = str(uuid.uuid4())
            new_report_query = ReportsTable.insert().values(
                report_id=report_id,
                owner_id=get_owner_id(self.request_user),
                creator_id=self.request_user["user_id"],
                source=new_export.source,
                report_template_id=new_export.export_id,
                source_type=new_export.source.list_type,
            )
            with Connection(self.current_shard_name) as (db_connection):
                db_connection.execute(new_report_query)
                event = create_event(
                    "export_asked",
                    get_owner_id(self.request_user),
                    self.request_user["user_id"],
                    data=dict(
                        export_template_id=new_export.export_id,
                        source=new_export.source,
                        name=report_data["name"],
                    ),
                    resource_type=EventResources.report,
                    resource_id=report_id,
                    shard=self.current_shard_name,
                )
                db_connection.execute(event)
                select_from = ReportsTable.join(
                    ReportTemplatesTable,
                    ReportTemplateRow.report_template_id
                    == ReportRow.report_template_id,
                )
                q = (
                    sql_select(
                        (
                            ReportRow,
                            ReportTemplateRow.name.label("template_name"),
                            ReportTemplateRow.type.label("template_type"),
                        )
                    )
                    .select_from(select_from)
                    .where(ReportRow.report_id == report_id)
                )
                report = db_connection.execute(q).fetchone()
                if report:
                    download = None
                    if report.status == "completed":
                        download = "/reports/download/" + report.result["file"]
                    report = ClsReport(
                        template_name=report.template_name,
                        generation_date=report.created,
                        report_id=report.report_id,
                        source=ClsReportSource(**report.source),
                        template_id=report.report_template_id,
                        status=report.status,
                        download=download,
                        template_type=report.template_type,
                    )
            return Http201(
                body=report, location=self.base_path + "/exports/" + report_id
            )
